﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Text;
using System.Linq;
namespace Common
{
    /// <summary>
    /// 验证实体属性合法性
    /// </summary>
    /// <remarks>
    /// <![CDATA[
    /// 提示：
    ///     1.必须向对System.ComponentModel.DataAnnotations.dll进行引用
    ///     2.如需要扩展特性，请继承： ValidationAttribute
    /// ]]>
    /// </remarks>
    /// <example>
    /// <code>
    /// <![CDATA[
    ///    class ModelVerifyDemo
    ///    {
    ///        public class Model
    ///        {
    ///            [Range(1, int.MaxValue, ErrorMessage = "{0}必须在{1}至{2}范围内")]
    ///            [Display(Name = "用户Id", GroupName = "Update|Delete|Select")]
    ///            public int NodeId { get; set; }
    ///
    ///            //其他验证..........
    ///            [StringLength(11, ErrorMessage = "{0}长度无效")]
    ///            [Required(ErrorMessage = "{0}不可为空")]
    ///            [Display(Name = "用户账号")]
    ///            public string NodeCode { get; set; }
    ///
    ///
    ///        }
    ///        public void TestModelVerify()
    ///        {
    ///             Model model = new Model();
    ///             model.NodeCode = "13428695847";
    ///
    ///             //方案1：验证是否正确
    ///             bool isSuccess = ModelVerify.IsSuccess(model);
    ///             Console.WriteLine("是否通过：" + isSuccess);
    ///
    ///             //方案2：获取第一个验证失败
    ///             var verify = ModelVerify.First(model);
    ///             if (!verify.IsNull())
    ///             {
    ///                 Console.WriteLine("属性名称：" + verify.PropertyName);
    ///                 Console.WriteLine("显示名称：" + verify.DisplayName);
    ///                 Console.WriteLine("错误信息：" + verify.ErrorMessage);
    ///             }
    ///             
    ///             //方案3：获取所有验证失败信息
    ///             var result = ModelVerify.Verify(model);
    ///             Console.WriteLine("是否通过：" + result.IsSuccess);
    ///             if (!result.IsSuccess)
    ///             {
    ///                 foreach (var item in result.Verifys)
    ///                 {
    ///                     Console.WriteLine("属性名称：" + item.PropertyName);
    ///                     Console.WriteLine("显示名称：" + item.DisplayName);
    ///                     Console.WriteLine("错误信息：" + item.ErrorMessage);
    ///                 }
    ///             }
    ///             
    ///             //方案4：分组验证
    ///             var groupResult = ModelVerify.First(model, "Update");
    ///             if (!groupResult.IsNull())
    ///             {
    ///                 Console.WriteLine("属性名称：" + groupResult.PropertyName);
    ///                 Console.WriteLine("显示名称：" + groupResult.DisplayName);
    ///                 Console.WriteLine("错误信息：" + groupResult.ErrorMessage);
    ///             }
    ///                  
    ///        }         
    ///    }             
    ///                  
    /// ]]>
    /// </code>
    /// </example>
    public static class ModelVerify
    {
        /// <summary>
        /// 验证实体
        /// </summary>
        /// <typeparam name="T">实体对象类型</typeparam>
        /// <param name="model">实体对象</param>
        /// <param name="groupName">
        ///        <para>作用：用于指定当前验证的数据组</para>
        ///        <para>前置：需要在特性上指定DisplayAttribute特性的GroupName，如果未指定则全部验证</para>
        ///        <para>提示：如需要一个属性在多个组中使用可以用“|”进行分离，例如：[Display(GroupName="Add|Update")]</para>
        ///        <para>使用：var result = ModelVerify.Verify(model,"Add");</para>
        /// </param>
        /// <remarks>
        /// 无
        /// </remarks>
        /// <example>
        /// <code>
        /// <![CDATA[
        ///    class ModelVerifyDemo
        ///    {
        ///        public class Model
        ///        {
        ///            [Range(1, int.MaxValue, ErrorMessage = "{0}必须在{1}至{2}范围内")]
        ///            [Display(Name = "用户Id", GroupName = "Update|Delete|Select")]
        ///            public int NodeId { get; set; }
        ///
        ///            //其他验证..........
        ///            [StringLength(11, ErrorMessage = "{0}长度无效")]
        ///            [Required(ErrorMessage = "{0}不可为空")]
        ///            [Display(Name = "用户账号")]
        ///            public string NodeCode { get; set; }
        ///
        ///
        ///        }
        ///        public void TestModelVerify()
        ///        {
        ///            Model model = new Model();
        ///            model.NodeCode = "13428695847";
        ///
        ///            //方案3：获取所有验证失败信息
        ///            var result = ModelVerify.Verify(model);
        ///            Console.WriteLine("是否通过：" + result.IsSuccess);
        ///            if (!result.IsSuccess)
        ///            {
        ///                foreach (var item in result.Verifys)
        ///                {
        ///                    Console.WriteLine("属性名称：" + item.PropertyName);
        ///                    Console.WriteLine("显示名称：" + item.DisplayName);
        ///                    Console.WriteLine("错误信息：" + item.ErrorMessage);
        ///                }
        ///            }
        ///        }
        ///    }
        /// ]]>
        /// </code>
        /// </example>
        /// <returns>返回验证结果，不会为Null</returns>
        public static ValidResult Verify<T>(T model, string groupName = "")
        {
            return VerifyObject(model, groupName, false);
        }

        /// <summary>
        /// 获取第一个验证失败的信息
        /// </summary>
        /// <typeparam name="T">实体对象类型</typeparam>
        /// <param name="model">实体对象</param>
        /// <param name="groupName">
        ///        <para>作用：用于指定当前验证的数据组</para>
        ///        <para>前置：需要在特性上指定DisplayAttribute特性的GroupName，如果未指定则全部验证</para>
        ///        <para>提示：如需要一个属性在多个组中使用可以用“|”进行分离，例如：[Display(GroupName="Add|Update")]</para>
        ///        <para>使用：var result = ModelVerify.Verify(model,"Add");</para>
        /// </param>
        /// <remarks>
        /// 无
        /// </remarks>
        /// <example>
        /// <code>
        /// <![CDATA[
        ///    class ModelVerifyDemo
        ///    {
        ///        public class Model
        ///        {
        ///            [Range(1, int.MaxValue, ErrorMessage = "{0}必须在{1}至{2}范围内")]
        ///            [Display(Name = "用户Id", GroupName = "Update|Delete|Select")]
        ///            public int NodeId { get; set; }
        /// 
        ///            //其他验证..........
        ///            [StringLength(11, ErrorMessage = "{0}长度无效")]
        ///            [Required(ErrorMessage = "{0}不可为空")]
        ///            [Display(Name = "用户账号")]
        ///            public string NodeCode { get; set; }
        ///        }
        ///
        ///        public void TestModelVerify()
        ///        {
        ///            Model model = new Model();
        ///            model.NodeCode = "13428695847";
        ///
        ///             //方案2：获取第一个验证失败
        ///             var verify = ModelVerify.First(model);
        ///             if (!verify.IsNull())
        ///             {
        ///                 Console.WriteLine("属性名称：" + verify.PropertyName);
        ///                 Console.WriteLine("显示名称：" + verify.DisplayName);
        ///                 Console.WriteLine("错误信息：" + verify.ErrorMessage);
        ///             }
        ///            
        ///        }
        ///    }
        /// ]]>
        /// </code>
        /// </example>
        /// <returns>第一个验证失败的信息</returns>
        public static VerifyInfo First<T>(T model, string groupName = "")
        {
            var result = VerifyObject(model, groupName, true);
            if (result.IsSuccess)
            {
                return null;
            }
            return result.Verifys[0];
        }

        /// <summary>
        /// 验证实体是否验证通过
        /// </summary>
        /// <typeparam name="T">实体对象类型</typeparam>
        /// <param name="model">实体对象</param>
        /// <param name="groupName">
        ///        <para>作用：用于指定当前验证的数据组</para>
        ///        <para>前置：需要在特性上指定DisplayAttribute特性的GroupName，如果未指定则全部验证</para>
        ///        <para>提示：如需要一个属性在多个组中使用可以用“|”进行分离，例如：[Display(GroupName="Add|Update")]</para>
        ///        <para>使用：var result = ModelVerify.Verify(model,"Add");</para>
        /// </param>
        /// <remarks>
        /// 无
        /// </remarks>
        /// <example>
        /// <code>
        /// <![CDATA[
        ///    class ModelVerifyDemo
        ///    {
        ///        public class Model
        ///        {
        ///            [Range(1, int.MaxValue, ErrorMessage = "{0}必须在{1}至{2}范围内")]
        ///            [Display(Name = "用户Id", GroupName = "Update|Delete|Select")]
        ///            public int NodeId { get; set; }
        /// 
        ///            //其他验证..........
        ///            [StringLength(11, ErrorMessage = "{0}长度无效")]
        ///            [Required(ErrorMessage = "{0}不可为空")]
        ///            [Display(Name = "用户账号")]
        ///            public string NodeCode { get; set; }
        ///        }
        ///
        ///        public void TestModelVerify()
        ///        {
        ///            Model model = new Model();
        ///            model.NodeCode = "13428695847";
        ///
        ///            //验证是否正确
        ///            bool isSuccess = ModelVerify.IsSuccess(model);
        ///            Console.WriteLine("是否通过：" + isSuccess);
        ///        }
        ///    }
        /// ]]>
        /// </code>
        /// </example>
        /// <returns>返回验证结果</returns>
        public static bool IsSuccess<T>(T model, string groupName = "")
        {
            return VerifyObject(model, groupName, true).IsSuccess;
        }

        /// <summary>
        /// 验证实体
        /// </summary>
        /// <typeparam name="T">实体对象类型</typeparam>
        /// <param name="model">实体对象</param>
        /// <param name="groupName">
        ///        <para>作用：用于指定当前验证的数据组</para>
        ///        <para>前置：需要在特性上指定DisplayAttribute特性的GroupName，如果未指定则全部验证</para>
        ///        <para>提示：如需要一个属性在多个组中使用可以用“|”进行分离，例如：[Display(GroupName="Add|Update")]</para>
        ///        <para>使用：var result = ModelVerify.Verify(model,"Add");</para>
        /// </param>
        /// <param name="failRollback">当发现有验证失败时是否回滚，停止检查</param>
        /// <returns>返回验证结果，不会为Null</returns>
        private static ValidResult VerifyObject<T>(T model, string groupName, bool failRollback)
        {
            ValidResult vModel = new ValidResult();
            vModel.Verifys = new List<VerifyInfo>();

            var propertys = model.GetType().GetProperties();
            string error = string.Empty;
            vModel.IsSuccess = true;
            foreach (var item in propertys)
            {
                //属性显示名称，默认为属性名称
                string displayName = item.Name;

                //获取显示名称的特性
                //item.GetCustomAttribute
                DisplayAttribute displayAttr = item.GetCustomAttribute<DisplayAttribute>();
                if (displayAttr != null)
                {
                    if (!string.IsNullOrWhiteSpace(displayAttr.GroupName))
                    {
                        if (string.IsNullOrWhiteSpace(groupName))
                        {
                            continue;
                        }
                        //属性分组
                        var groupList = displayAttr.GroupName.Split('|');
                        //如果属性有分组，并且验证时有分组指定，并指定组不存在此属性组中，则不进行验证
                        if (!groupList.Contains(groupName))
                        {
                            continue;
                        }
                    }
                    displayName = displayAttr.Name;
                }

                //属性内存中的值
                object value = item.GetValue(model, null);

                //获取所有验证特性，并循环验证
                ValidationAttribute[] attrColl = item.GetCustomAttributes(typeof(ValidationAttribute), true) as ValidationAttribute[];
                foreach (var attr in attrColl)
                {
                    VerifyInfo vrModel = new VerifyInfo();
                    if (!attr.IsValid(value))
                    {
                        //获取自定义的错误信息;
                        error = attr.FormatErrorMessage(displayName);
                        vModel.IsSuccess = false;
                        vrModel.DisplayName = displayName;
                        vrModel.PropertyName = item.Name;
                        vrModel.ErrorMessage = error;
                        vModel.Verifys.Add(vrModel);
                        if (failRollback)
                        {
                            return vModel;
                        }
                    }
                }
            }
            return vModel;
        }
    }
    /// <summary>
    /// 验证结果
    /// </summary>
    public class ValidResult
    {
        /// <summary>
        /// 验证是否通过
        /// </summary>
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 验证失败的验证信息集合
        /// </summary>
        public List<VerifyInfo> Verifys { get; set; }
    }

    /// <summary>
    /// 验证信息
    /// </summary>
    public class VerifyInfo
    {
        /// <summary>
        /// 属性名称，如：UserName
        /// </summary>
        public string PropertyName { get; set; }

        /// <summary>
        /// 属性显示名称，如：用户姓名
        /// </summary>
        public string DisplayName { get; set; }
        /// <summary>
        /// 错误信息
        /// </summary>
        public string ErrorMessage { get; set; }
    }

    class ModelVerifyDemo
    {
        public class Model
        {
            [Range(1, int.MaxValue, ErrorMessage = "{0}必须在{1}至{2}范围内")]
            [Display(Name = "用户Id", GroupName = "Update|Delete|Select")]
            public int NodeId { get; set; }

            //其他验证..........
            [StringLength(11, ErrorMessage = "{0}长度无效")]
            [Required(ErrorMessage = "{0}不可为空")]
            [Display(Name = "用户账号")]
            public string NodeCode { get; set; }


        }
        public void TestModelVerify()
        {
            Model model = new Model();
            model.NodeCode = "13428695847";

            //方案1：验证是否正确
            bool isSuccess = ModelVerify.IsSuccess(model);
            Console.WriteLine("是否通过：" + isSuccess);

            //方案2：获取第一个验证失败
            var verify = ModelVerify.First(model);
            if (verify != null)
            {
                Console.WriteLine("属性名称：" + verify.PropertyName);
                Console.WriteLine("显示名称：" + verify.DisplayName);
                Console.WriteLine("错误信息：" + verify.ErrorMessage);
            }

            //方案3：获取所有验证失败信息
            var result = ModelVerify.Verify(model);
            Console.WriteLine("是否通过：" + result.IsSuccess);
            if (!result.IsSuccess)
            {
                foreach (var item in result.Verifys)
                {
                    Console.WriteLine("属性名称：" + item.PropertyName);
                    Console.WriteLine("显示名称：" + item.DisplayName);
                    Console.WriteLine("错误信息：" + item.ErrorMessage);
                }
            }

            //方案4：分组验证
            var groupResult = ModelVerify.First(model, "Update");
            if (groupResult != null)
            {
                Console.WriteLine("属性名称：" + groupResult.PropertyName);
                Console.WriteLine("显示名称：" + groupResult.DisplayName);
                Console.WriteLine("错误信息：" + groupResult.ErrorMessage);
            }
        }
    }
}
